Conversation
WalkthroughAdds feature-gated N4W1 backup/recovery support across firmware and tests: CI matrix and artifact names gain an Sequence Diagram(s)sequenceDiagram
autonumber
participant User as "User"
participant UI as "Device UI"
participant FW as "Firmware handler"
participant Debug as "DebugLink/Emulator"
participant Host as "Test harness / CI"
User->>UI: select backup method (Display | N4W1)
UI->>FW: start backup/reset with chosen method
alt method == N4W1
FW->>UI: prompt to scan/write N4W1 tag
UI->>Debug: request backup_read / backup_write
Debug-->>UI: deliver tag payload or status
UI->>FW: forward payload/status
FW->>FW: decode mnemonic, validate incrementally
alt damaged/empty/non-empty
FW->>UI: show n4w1__err_damaged / n4w1__err_empty / n4w1__err_nonempty
UI->>User: request retry / new tag
User->>UI: retry scan/write
else valid
FW->>UI: show progress & continue
UI->>Host: report completion
end
else
FW->>UI: perform Display flow (show/confirm words)
UI->>Host: report completion
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsTimed out fetching pipeline failures after 30000ms Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.Comment |
4353da1 to
33e6532
Compare
099bfb0 to
14265bc
Compare
8fe2388 to
94b032e
Compare
ac4c601 to
1df8713
Compare
94b032e to
233d9b9
Compare
671ea4e to
9af64ff
Compare
2c32f2f to
0429757
Compare
0429757 to
c9b03d0
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
tests/input_flows.py (1)
2824-2828: Prefer explicit method allow-list in recovery constructors.Line 2824 and Line 2855 currently treat any non-
Displayvalue as the “non-word-count path”. That works forN4W1, but it can silently accept unsupported future values. Fail fast with an explicit allow-list.Suggested hardening diff
@@ - self.method = method - if method is messages.BackupMethod.Display: + self.method = method + if method not in ( + messages.BackupMethod.Display, + messages.BackupMethod.N4W1, + ): + raise ValueError(f"Unsupported backup method: {method!r}") + if method is messages.BackupMethod.Display: self.word_count = len(shares[0].split(" ")) else: self.word_count = None @@ - self.method = method - if self.method is messages.BackupMethod.Display: + self.method = method + if self.method not in ( + messages.BackupMethod.Display, + messages.BackupMethod.N4W1, + ): + raise ValueError(f"Unsupported backup method: {self.method!r}") + if self.method is messages.BackupMethod.Display: self.word_count = len(shares[0].split(" ")) else: self.word_count = NoneAlso applies to: 2855-2859
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/input_flows.py` around lines 2824 - 2828, Replace the broad "else" handling that treats any non-Display BackupMethod as the non-word-count path with an explicit allow-list: in the blocks that check "if method is messages.BackupMethod.Display" (and the similar block around lines 2855-2859), explicitly branch for each supported enum value (e.g., "elif method is messages.BackupMethod.<SUPPORTED_METHOD>" for methods that should set word_count = None) and add a final "else: raise ValueError(f'Unsupported BackupMethod: {method}')" to fail fast on unknown future values so the constructors (where word_count is assigned) only accept known BackupMethod members.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/core.yml:
- Around line 180-183: The workflow's core_emu_arm job contains an invalid
exclude block referencing values not present in its matrix (exclude: - type:
normal, asan: asan), which breaks linting; remove the invalid exclude entries
(or replace them with valid combinations matching the matrix keys type:
[debuglink] and asan: [noasan]) so the exclude only refers to actual matrix
values for the core_emu_arm job.
- Around line 309-314: The upload-artifact name template omits the n4w1 flag so
jobs with n4w1=true collide with base matrix artifacts; update the
upload-artifact step (the one that currently produces names like
core-test-device-T3W1-universal-en-noasan) to include the n4w1 suffix the same
way the download-artifact step does by appending a conditional expression using
matrix.n4w1 (e.g. append a conditional "-n4w1" via a GitHub Actions expression
such as ${{ matrix.n4w1 && '-n4w1' || '' }} to the artifact name) so artifact
names become unique when n4w1 is set.
In `@core/src/apps/management/recovery_device/layout.py`:
- Line 338: Replace the hardcoded English literal raised in RetryRead with a
translation key lookup; instead of raise RetryRead("This tag is empty. Continue
to scan a different tag.") call the project's i18n helper (e.g. _(...) or
translate(...)) with a new key such as "recovery_device.empty_tag" and use that
returned string in RetryRead (keep the exception type RetryRead unchanged and
add the new translation key to the locale files).
- Around line 387-389: Wrap the blob.decode() call in a try/except
UnicodeDecodeError around the code that assigns share and calls self.check_words
so malformed bytes do not raise; on UnicodeDecodeError return a recoverable
validation error through the existing validation/error path (do not let the
exception propagate) — e.g., catch UnicodeDecodeError when decoding blob, and
call the module's validation helper (reuse the same error-return mechanism used
elsewhere) to report "tag payload is not valid UTF-8" instead of crashing; keep
the rest of the flow (the call to self.check_words(share)) unchanged for the
successful decode branch.
In `@core/src/apps/management/reset_device/layout.py`:
- Around line 269-275: The warning description currently passes a hardcoded
English string via exc.msg to trezorui_api.show_warning inside
raise_if_not_confirmed (see raise_if_not_confirmed and trezorui_api.show_warning
usage); replace that hardcoded text with a translation lookup from TR (add a new
key such as TR.reset_device__non_empty_n4w1_tag or use an existing appropriate
key) and pass TR.<key> as the description instead of exc.msg, and apply the same
change to the other occurrence around the retry block (~line 295) so both
warnings are localized.
In `@tests/device_tests/reset_recovery/test_recovery_slip39_basic.py`:
- Line 71: The pytest param id is incorrect: the test currently uses the string
"3of5-33" for a vector that is actually a 2-of-5 case; update the identifier to
"2of5-33" (replace the "3of5-33" literal used in the test parameter list in
test_recovery_slip39_basic.py) so test reports and failure messages correctly
reflect the 2-of-5 vector.
---
Nitpick comments:
In `@tests/input_flows.py`:
- Around line 2824-2828: Replace the broad "else" handling that treats any
non-Display BackupMethod as the non-word-count path with an explicit allow-list:
in the blocks that check "if method is messages.BackupMethod.Display" (and the
similar block around lines 2855-2859), explicitly branch for each supported enum
value (e.g., "elif method is messages.BackupMethod.<SUPPORTED_METHOD>" for
methods that should set word_count = None) and add a final "else: raise
ValueError(f'Unsupported BackupMethod: {method}')" to fail fast on unknown
future values so the constructors (where word_count is assigned) only accept
known BackupMethod members.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2a5da0be-6fc9-422d-97df-3cac922efd84
📒 Files selected for processing (19)
.github/workflows/core.ymlcore/src/apps/management/recovery_device/layout.pycore/src/apps/management/reset_device/layout.pycore/src/trezor/ui/layouts/eckhart/recovery.pytests/conftest.pytests/device_tests/reset_recovery/test_recovery_bip39_dryrun.pytests/device_tests/reset_recovery/test_recovery_bip39_t2.pytests/device_tests/reset_recovery/test_recovery_slip39_advanced.pytests/device_tests/reset_recovery/test_recovery_slip39_advanced_dryrun.pytests/device_tests/reset_recovery/test_recovery_slip39_basic.pytests/device_tests/reset_recovery/test_recovery_slip39_basic_dryrun.pytests/device_tests/reset_recovery/test_reset_recovery_bip39.pytests/device_tests/reset_recovery/test_reset_recovery_slip39_advanced.pytests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.pytests/device_tests/reset_recovery/test_reset_slip39_basic.pytests/device_tests/test_repeated_backup.pytests/input_flows.pytests/input_flows_helpers.pytests/ui_tests/fixtures.json
4b31e1f to
1f936e1
Compare
[no changelog]
[no changelog]
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
core/src/apps/management/reset_device/layout.py (1)
237-243: Finish the missing N4W1 intro/safety screens.
intro()is still empty andbackup()skips the warning shown by_DisplayBackup, so this path drops straight into writing secret shares to tags. I'd close those TODOs before enabling the flow outside demo builds.Do you want me to sketch a minimal version that reuses the existing backup intro/warning layouts?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/src/apps/management/reset_device/layout.py` around lines 237 - 243, The intro() and backup() stubs in ResetDevice.layout must present the same N4W1 intro and safety screens used elsewhere instead of being no-ops; implement intro(self, num_of_words) to render the existing backup/introduction text/UI (reuse the layout or components used by the prior backup flow), and update backup(self, iter_shares) to invoke the existing _DisplayBackup warning/confirmation sequence before proceeding to write shares (so the user must acknowledge safety). Locate and call the same helper/layout functions or components used by the current backup flow (e.g., the backup intro screen and _DisplayBackup warning) inside ResetDevice.layout.intro and ResetDevice.layout.backup, preserving the num_of_words and iter_shares parameters.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@core/src/apps/management/recovery_device/layout.py`:
- Around line 283-290: The current warning in choose_handler logs "Unsupported
backup method: None" for the intentional default path; change the condition so
that the warning is only emitted for explicit unsupported methods, not when
method is None. Concretely, update the conditional that checks method (in
function choose_handler) to first ensure method is not None and not
BackupMethod.Display before entering the __debug__ logging branch and calling
trezor.log.warning, so None is treated as the silent placeholder.
---
Nitpick comments:
In `@core/src/apps/management/reset_device/layout.py`:
- Around line 237-243: The intro() and backup() stubs in ResetDevice.layout must
present the same N4W1 intro and safety screens used elsewhere instead of being
no-ops; implement intro(self, num_of_words) to render the existing
backup/introduction text/UI (reuse the layout or components used by the prior
backup flow), and update backup(self, iter_shares) to invoke the existing
_DisplayBackup warning/confirmation sequence before proceeding to write shares
(so the user must acknowledge safety). Locate and call the same helper/layout
functions or components used by the current backup flow (e.g., the backup intro
screen and _DisplayBackup warning) inside ResetDevice.layout.intro and
ResetDevice.layout.backup, preserving the num_of_words and iter_shares
parameters.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 758731a6-ed4f-4b4c-b9f9-5b278cb2b8c9
⛔ Files ignored due to path filters (1)
core/embed/rust/src/translations/generated/translated_string.rsis excluded by!**/generated/**
📒 Files selected for processing (9)
.github/workflows/core.ymlcore/embed/rust/librust_qstr.hcore/mocks/trezortranslate_keys.pyicore/src/apps/management/recovery_device/layout.pycore/src/apps/management/reset_device/layout.pycore/translations/en.jsoncore/translations/order.jsoncore/translations/signatures.jsontests/device_tests/reset_recovery/test_recovery_slip39_basic.py
✅ Files skipped from review due to trivial changes (5)
- core/embed/rust/librust_qstr.h
- core/translations/signatures.json
- core/translations/en.json
- core/translations/order.json
- core/mocks/trezortranslate_keys.pyi
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/device_tests/reset_recovery/test_recovery_slip39_basic.py
1f936e1 to
c5d047b
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/core.yml (1)
305-356:⚠️ Potential issue | 🟠 MajorKeep N4W1 coverage separate from the default T3W1 report.
This include row adds a second
T3W1/universal/noasanbinary withN4W1=1, but the coverage pipeline still reuses only the standardcore-emu-T3W1-universal-debuglink-noasanbuild for.isources. That mixes coverage from two different compiled variants and can skew or break the final T3W1 coverage report. Either skipupload-coverageformatrix.n4w1for now, or make the coverage artifacts/report job variant-aware (-n4w1sources + separate merge/reporting).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/core.yml around lines 305 - 356, The pipeline is mixing coverage from the N4W1 variant into the standard T3W1 report because matrix.n4w1 produces a second core-emu artifact but upload-coverage always processes the default build; either prevent uploading coverage for N4W1 by adding a condition to the upload-coverage step (if: ${{ !matrix.n4w1 }}) or make the coverage flow variant-aware by including the n4w1 suffix in the core-emu download/upload artifact names (core-emu-${{ matrix.model }}-${{ matrix.coins }}-debuglink-${{ matrix.asan }}${{ matrix.n4w1 && '-n4w1' || '' }}) and updating the upload-coverage job to merge/report per-variant artifacts instead of a single default artifact.
♻️ Duplicate comments (1)
core/src/apps/management/recovery_device/layout.py (1)
283-291:⚠️ Potential issue | 🟡 MinorDon't warn on the intentional
method=Nonepath.When
USE_N4W1is off, this still logsUnsupported backup method: Nonefor the normal default flow. Limit the warning to explicit non-Displayvalues.Proposed fix
- if method is not BackupMethod.Display and __debug__: + if method not in (None, BackupMethod.Display) and __debug__: from trezor import log log.warning(__name__, "Unsupported backup method: %s", method)Based on learnings,
method=Nonepassed tochoose_backup_method()is intentionally distinct fromBackupMethod.Display, and debug-mode unsupported-method warnings should be suppressed forNoneas well as forBackupMethod.Display.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/src/apps/management/recovery_device/layout.py` around lines 283 - 291, The warning in choose_handler currently triggers for method==None; change the condition so the debug warning only runs when method is neither None nor BackupMethod.Display: update the if to check "method is not None and method is not BackupMethod.Display and __debug__" (keep the import of BackupMethod and return of _DisplayHandler unchanged) so intentional None flows do not log an unsupported-method warning.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@core/src/apps/management/reset_device/layout.py`:
- Around line 241-259: The branch in _backup_share misinterprets ShareInfo.index
as a global position; change backup to materialize iter_shares into a flattened
list (so you can compute global_index and total_shares across groups) and pass
those global values into _backup_share (or adjust _backup_share signature to
accept global_index and total_shares) so the description/button decision uses
global_index == 0 and global_index == total_shares - 1; update any callers
(e.g., slip39_advanced_show_and_confirm_shares) to rely on the flattened order
instead of group-local ShareInfo.index/num_of_shares.
In `@core/src/trezor/ui/layouts/eckhart/recovery.py`:
- Around line 224-236: The choose_method function currently indexes a two-item
tuple with the result of trezorui_api.select_word(), which can return 0..3 and
will raise IndexError for values >=2; replace the tuple lookup with an explicit
conditional mapping in choose_method: call trezorui_api.select_word(...) as
before, then if index == 0 return BackupMethod.N4W1, elif index == 1 return
BackupMethod.Display, elif index in (2, 3) return a safe default (e.g.,
BackupMethod.Display) or raise a clear ValueError for unexpected slots;
reference the choose_method function, trezorui_api.select_word, BackupMethod and
the TR.backup__type_* symbols when making the change.
---
Outside diff comments:
In @.github/workflows/core.yml:
- Around line 305-356: The pipeline is mixing coverage from the N4W1 variant
into the standard T3W1 report because matrix.n4w1 produces a second core-emu
artifact but upload-coverage always processes the default build; either prevent
uploading coverage for N4W1 by adding a condition to the upload-coverage step
(if: ${{ !matrix.n4w1 }}) or make the coverage flow variant-aware by including
the n4w1 suffix in the core-emu download/upload artifact names (core-emu-${{
matrix.model }}-${{ matrix.coins }}-debuglink-${{ matrix.asan }}${{ matrix.n4w1
&& '-n4w1' || '' }}) and updating the upload-coverage job to merge/report
per-variant artifacts instead of a single default artifact.
---
Duplicate comments:
In `@core/src/apps/management/recovery_device/layout.py`:
- Around line 283-291: The warning in choose_handler currently triggers for
method==None; change the condition so the debug warning only runs when method is
neither None nor BackupMethod.Display: update the if to check "method is not
None and method is not BackupMethod.Display and __debug__" (keep the import of
BackupMethod and return of _DisplayHandler unchanged) so intentional None flows
do not log an unsupported-method warning.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 71009308-ea25-4473-9716-294aed30b1e5
⛔ Files ignored due to path filters (1)
core/embed/rust/src/translations/generated/translated_string.rsis excluded by!**/generated/**
📒 Files selected for processing (24)
.github/workflows/core.ymlcore/embed/rust/librust_qstr.hcore/mocks/trezortranslate_keys.pyicore/src/apps/management/recovery_device/layout.pycore/src/apps/management/reset_device/layout.pycore/src/trezor/ui/layouts/eckhart/recovery.pycore/translations/en.jsoncore/translations/order.jsoncore/translations/signatures.jsontests/conftest.pytests/device_tests/reset_recovery/test_recovery_bip39_dryrun.pytests/device_tests/reset_recovery/test_recovery_bip39_t2.pytests/device_tests/reset_recovery/test_recovery_slip39_advanced.pytests/device_tests/reset_recovery/test_recovery_slip39_advanced_dryrun.pytests/device_tests/reset_recovery/test_recovery_slip39_basic.pytests/device_tests/reset_recovery/test_recovery_slip39_basic_dryrun.pytests/device_tests/reset_recovery/test_reset_recovery_bip39.pytests/device_tests/reset_recovery/test_reset_recovery_slip39_advanced.pytests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.pytests/device_tests/reset_recovery/test_reset_slip39_basic.pytests/device_tests/test_repeated_backup.pytests/input_flows.pytests/input_flows_helpers.pytests/ui_tests/fixtures.json
✅ Files skipped from review due to trivial changes (6)
- tests/device_tests/reset_recovery/test_recovery_bip39_t2.py
- core/translations/signatures.json
- core/translations/en.json
- core/embed/rust/librust_qstr.h
- core/mocks/trezortranslate_keys.pyi
- core/translations/order.json
🚧 Files skipped from review as they are similar to previous changes (12)
- tests/device_tests/reset_recovery/test_recovery_slip39_advanced_dryrun.py
- tests/device_tests/reset_recovery/test_reset_recovery_slip39_advanced.py
- tests/device_tests/reset_recovery/test_reset_recovery_bip39.py
- tests/device_tests/reset_recovery/test_recovery_bip39_dryrun.py
- tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.py
- tests/device_tests/reset_recovery/test_recovery_slip39_advanced.py
- tests/device_tests/reset_recovery/test_reset_slip39_basic.py
- tests/device_tests/reset_recovery/test_recovery_slip39_basic.py
- tests/input_flows_helpers.py
- tests/device_tests/test_repeated_backup.py
- tests/device_tests/reset_recovery/test_recovery_slip39_basic_dryrun.py
- tests/input_flows.py
There was a problem hiding this comment.
🧹 Nitpick comments (1)
.github/workflows/core.yml (1)
334-339: Don't silently drop UI checks on the N4W1 universal/noasan path.For this matrix entry
ACTIONS_DO_UI_TESTis still true, but the N4W1 branch switches totest_emu_multicoreinstead oftest_emu_ui_multicore. That loses the--ui=test --ui-check-missing --do-master-diffcoverage the standard path gets for the same job shape.Possible fix
- name: Run device tests (N4W1) if: ${{ matrix.n4w1 }} # TODO: test N4W1 UI fixtures as well - run: nix-shell --run "uv run make -C core ${{ env.ACTIONS_DO_UI_TEST == 'true' && 'test_emu_multicore' || 'test_emu' }}" + run: nix-shell --run "uv run make -C core ${{ env.ACTIONS_DO_UI_TEST == 'true' && 'test_emu_ui_multicore' || 'test_emu' }}"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/core.yml around lines 334 - 339, The N4W1 matrix branch incorrectly uses test_emu_multicore instead of the UI-aware target, which drops UI checks when ACTIONS_DO_UI_TEST is true; update the N4W1 run line to mirror the non-N4W1 conditional expression so it selects test_emu_ui_multicore when ACTIONS_DO_UI_TEST is 'true' (i.e., use the same ternary logic with ACTIONS_DO_UI_TEST -> 'test_emu_ui_multicore' || 'test_emu' instead of switching to 'test_emu_multicore'), ensuring the UI flags (--ui=test, --ui-check-missing, --do-master-diff) are preserved for N4W1 runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In @.github/workflows/core.yml:
- Around line 334-339: The N4W1 matrix branch incorrectly uses
test_emu_multicore instead of the UI-aware target, which drops UI checks when
ACTIONS_DO_UI_TEST is true; update the N4W1 run line to mirror the non-N4W1
conditional expression so it selects test_emu_ui_multicore when
ACTIONS_DO_UI_TEST is 'true' (i.e., use the same ternary logic with
ACTIONS_DO_UI_TEST -> 'test_emu_ui_multicore' || 'test_emu' instead of switching
to 'test_emu_multicore'), ensuring the UI flags (--ui=test, --ui-check-missing,
--do-master-diff) are preserved for N4W1 runs.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ab51a135-d1d8-47a2-b5d1-53e25c5308bb
📒 Files selected for processing (1)
.github/workflows/core.yml
Can be manually tested by invoking: ``` $ core/emu.py -ea -c trezorctl device setup -b shamir # will run multi-share backup $ core/tools/n1w1-emu.py run 127.0.0.1:21325 /tmp/tagN # simulate tag connection and I/O ``` Enabled N4W1-based backup/recovery device tests for SLIP-39 single group scenarios. Other device & click tests will be added in subsequent PRs. [no changelog]
[no changelog]
b8e841b to
53dcb05
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
core/src/trezor/ui/layouts/eckhart/recovery.py (1)
224-236:⚠️ Potential issue | 🟠 MajorHandle unexpected
select_word()values explicitly.This still relies on a 2-item tuple lookup, so any unexpected index turns into a generic
IndexError. Given the UI contract here, it should fail deliberately and readably instead.Proposed fix
index = await interact( trezorui_api.select_word( title=title, description=description, words=(TR.backup__type_n4w1, TR.backup__type_wordlist, ""), ), br_name="choose_method", ) - return (BackupMethod.N4W1, BackupMethod.Display)[index] + if index == 0: + return BackupMethod.N4W1 + if index == 1: + return BackupMethod.Display + raise ValueError(f"Unexpected select_word index: {index}")Based on learnings,
trezorui_api.select_word()called fromchoose_methodwill only return 0 or 1 on Eckhart because the empty third word slot is disabled at the UI level. Unexpected return values (index >= 2) should trigger an explicit failure (e.g.,raise ValueError) rather than silently defaulting toBackupMethod.Display.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/src/trezor/ui/layouts/eckhart/recovery.py` around lines 224 - 236, The choose_method function uses the index from trezorui_api.select_word to pick from a two-item tuple, which will raise a generic IndexError for unexpected values; change choose_method (the async function calling trezorui_api.select_word) to validate the returned index explicitly: accept 0 -> BackupMethod.N4W1, 1 -> BackupMethod.Display, and if index is any other value raise a clear ValueError (or custom error) with a descriptive message referencing the unexpected index and the select_word call so failures are deliberate and readable.core/src/apps/management/recovery_device/layout.py (1)
283-290:⚠️ Potential issue | 🟡 MinorSuppress the debug warning for
method=Nonehere as well.
Noneis the intentional default path, so this branch still logsUnsupported backup method: Noneon ordinary recovery calls whenUSE_N4W1is off.Proposed fix
- if method is not BackupMethod.Display and __debug__: + if method not in (None, BackupMethod.Display) and __debug__: from trezor import log log.warning(__name__, "Unsupported backup method: %s", method)Based on learnings,
method=Nonepassed tochoose_backup_method()is intentionally distinct fromBackupMethod.Display.Noneis reserved for a future flow where the user is prompted to choose between supported backup methods interactively. It should NOT be treated as equivalent toBackupMethod.Display, and debug-mode unsupported-method warnings should be suppressed forNone(as well as forBackupMethod.Display).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/src/apps/management/recovery_device/layout.py` around lines 283 - 290, The debug warning in choose_handler currently fires for method=None; update the condition so it only logs when method is neither None nor BackupMethod.Display (i.e., skip logging for the intentional default None path as well as the Display enum). Modify the branch that checks BackupMethod to require method is not None and method is not BackupMethod.Display before calling log.warning (refer to function choose_handler, symbol BackupMethod, __debug__, and log.warning).
🧹 Nitpick comments (2)
.github/workflows/core.yml (1)
55-69: Consider adding abtconlyN4W1 lane too.All new N4W1 matrix rows are
coins: universal, so CI still won’t build or exercise thebtconlyvariant. If N4W1 is expected to ship on both firmware types, this leaves a blind spot for btconly-only regressions.Based on learnings, when advertising the
Capability.N4W1capability, reviewers should ensure it is added for all firmware types (both Bitcoin-only and universal builds). Do not wrapf.capabilities.append(Capability.N4W1)withutils.BITCOIN_ONLY. The only acceptable conditional guard isutils.USE_N4W1.Also applies to: 133-138, 306-311
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/core.yml around lines 55 - 69, The CI matrix is missing a btconly N4W1 lane and some code conditionally guards N4W1 with utils.BITCOIN_ONLY; add a matrix row mirroring the existing N4W1 universal entry but with coins: btconly and n4w1: true (e.g., alongside the T3W1 n4w1 entry) so btconly builds are exercised, and remove any conditional guards that append Capability.N4W1 under utils.BITCOIN_ONLY — ensure Capability.N4W1 is only gated by utils.USE_N4W1; apply the same change to the other similar blocks that add N4W1 (the other matrix sections and any code paths that call f.capabilities.append(Capability.N4W1)).tests/input_flows.py (1)
2227-2242: Add an explicit assert for method validation to matchInputFlowSlip39BasicBackup.In
InputFlowSlip39BasicBackup.input_flow_eckhart, there's an explicitassert self.method in (Display, N4W1). Here, the dict lookup implicitly validates valid methods, but an invalid method would raise a crypticKeyErrorinstead of a clearAssertionError.♻️ Suggested fix for consistency
def input_flow_eckhart(self) -> BRGeneratorType: + assert self.method in ( + messages.BackupMethod.Display, + messages.BackupMethod.N4W1, + ) num_screens = { messages.BackupMethod.Display: 10, messages.BackupMethod.N4W1: 8, }[self.method]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/input_flows.py` around lines 2227 - 2242, Add an explicit assertion to validate self.method at the start of input_flow_eckhart so it mirrors InputFlowSlip39BasicBackup: assert self.method in (messages.BackupMethod.Display, messages.BackupMethod.N4W1). This makes method validation explicit and raises an AssertionError with a clear failure mode before using the dict lookup that currently raises a KeyError; place the assert immediately before the num_screens assignment in the input_flow_eckhart method.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@core/src/apps/management/reset_device/layout.py`:
- Around line 237-246: The intro() method is currently a no-op and backup()
immediately writes shares, skipping the pre-export safety screens; implement
intro() to reuse the existing _DisplayBackup intro/warning flow (e.g., await
self._display_backup.intro(num_of_words) or delegate to the _DisplayBackup.intro
method) and ensure backup() calls await self.intro(num_of_words) (or awaits the
same _DisplayBackup warning) before iterating and calling _backup_share(share)
so the N4W1 path shows the same safety/acknowledgement screens as
_DisplayBackup.
---
Duplicate comments:
In `@core/src/apps/management/recovery_device/layout.py`:
- Around line 283-290: The debug warning in choose_handler currently fires for
method=None; update the condition so it only logs when method is neither None
nor BackupMethod.Display (i.e., skip logging for the intentional default None
path as well as the Display enum). Modify the branch that checks BackupMethod to
require method is not None and method is not BackupMethod.Display before calling
log.warning (refer to function choose_handler, symbol BackupMethod, __debug__,
and log.warning).
In `@core/src/trezor/ui/layouts/eckhart/recovery.py`:
- Around line 224-236: The choose_method function uses the index from
trezorui_api.select_word to pick from a two-item tuple, which will raise a
generic IndexError for unexpected values; change choose_method (the async
function calling trezorui_api.select_word) to validate the returned index
explicitly: accept 0 -> BackupMethod.N4W1, 1 -> BackupMethod.Display, and if
index is any other value raise a clear ValueError (or custom error) with a
descriptive message referencing the unexpected index and the select_word call so
failures are deliberate and readable.
---
Nitpick comments:
In @.github/workflows/core.yml:
- Around line 55-69: The CI matrix is missing a btconly N4W1 lane and some code
conditionally guards N4W1 with utils.BITCOIN_ONLY; add a matrix row mirroring
the existing N4W1 universal entry but with coins: btconly and n4w1: true (e.g.,
alongside the T3W1 n4w1 entry) so btconly builds are exercised, and remove any
conditional guards that append Capability.N4W1 under utils.BITCOIN_ONLY — ensure
Capability.N4W1 is only gated by utils.USE_N4W1; apply the same change to the
other similar blocks that add N4W1 (the other matrix sections and any code paths
that call f.capabilities.append(Capability.N4W1)).
In `@tests/input_flows.py`:
- Around line 2227-2242: Add an explicit assertion to validate self.method at
the start of input_flow_eckhart so it mirrors InputFlowSlip39BasicBackup: assert
self.method in (messages.BackupMethod.Display, messages.BackupMethod.N4W1). This
makes method validation explicit and raises an AssertionError with a clear
failure mode before using the dict lookup that currently raises a KeyError;
place the assert immediately before the num_screens assignment in the
input_flow_eckhart method.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a039282f-6b92-43a3-9da0-5db463dc2c7d
📒 Files selected for processing (19)
.github/workflows/core.ymlcore/src/apps/management/recovery_device/layout.pycore/src/apps/management/reset_device/layout.pycore/src/trezor/ui/layouts/eckhart/recovery.pytests/conftest.pytests/device_tests/reset_recovery/test_recovery_bip39_dryrun.pytests/device_tests/reset_recovery/test_recovery_bip39_t2.pytests/device_tests/reset_recovery/test_recovery_slip39_advanced.pytests/device_tests/reset_recovery/test_recovery_slip39_advanced_dryrun.pytests/device_tests/reset_recovery/test_recovery_slip39_basic.pytests/device_tests/reset_recovery/test_recovery_slip39_basic_dryrun.pytests/device_tests/reset_recovery/test_reset_recovery_bip39.pytests/device_tests/reset_recovery/test_reset_recovery_slip39_advanced.pytests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.pytests/device_tests/reset_recovery/test_reset_slip39_basic.pytests/device_tests/test_repeated_backup.pytests/input_flows.pytests/input_flows_helpers.pytests/ui_tests/fixtures.json
✅ Files skipped from review due to trivial changes (3)
- tests/device_tests/reset_recovery/test_recovery_slip39_advanced_dryrun.py
- tests/device_tests/reset_recovery/test_recovery_bip39_t2.py
- tests/device_tests/reset_recovery/test_recovery_slip39_advanced.py
🚧 Files skipped from review as they are similar to previous changes (6)
- tests/conftest.py
- tests/device_tests/reset_recovery/test_recovery_slip39_basic_dryrun.py
- tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.py
- tests/device_tests/reset_recovery/test_reset_recovery_bip39.py
- tests/device_tests/reset_recovery/test_reset_slip39_basic.py
- tests/device_tests/reset_recovery/test_recovery_slip39_basic.py
| async def intro(self, num_of_words: int | None = None) -> None: | ||
| # TODO: design/copy | ||
| pass | ||
|
|
||
| async def backup(self, iter_shares: Iterable[ShareInfo]) -> None: | ||
| # TODO: warn user about safety | ||
|
|
||
| # backup all shares | ||
| for share in iter_shares: | ||
| await self._backup_share(share) |
There was a problem hiding this comment.
Don’t skip the backup intro/safety screens on the N4W1 path.
intro() is a no-op and backup() jumps straight into writing shares, so this path misses the same pre-export safety acknowledgment that _DisplayBackup shows before exposing the seed.
Possible stopgap
class _N4W1Backup:
async def intro(self, num_of_words: int | None = None) -> None:
- # TODO: design/copy
- pass
+ from trezor.ui.layouts.reset import show_intro_backup
+
+ await show_intro_backup(num_of_words=num_of_words)
async def backup(self, iter_shares: Iterable[ShareInfo]) -> None:
- # TODO: warn user about safety
+ from trezor.ui.layouts.reset import show_warning_backup
+
+ await show_warning_backup()
# backup all shares
for share in iter_shares:
await self._backup_share(share)I can help sketch a minimal patch that reuses the existing intro/warning screens until the N4W1-specific copy lands.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@core/src/apps/management/reset_device/layout.py` around lines 237 - 246, The
intro() method is currently a no-op and backup() immediately writes shares,
skipping the pre-export safety screens; implement intro() to reuse the existing
_DisplayBackup intro/warning flow (e.g., await
self._display_backup.intro(num_of_words) or delegate to the _DisplayBackup.intro
method) and ensure backup() calls await self.intro(num_of_words) (or awaits the
same _DisplayBackup warning) before iterating and calling _backup_share(share)
so the N4W1 path shows the same safety/acknowledgement screens as
_DisplayBackup.
There was a problem hiding this comment.
♻️ Duplicate comments (2)
core/src/apps/management/reset_device/layout.py (1)
237-246:⚠️ Potential issue | 🟠 MajorReuse the existing backup disclosure flow before N4W1 writes.
intro()is a no-op andbackup()starts exporting shares immediately, so the N4W1 path skips the intro/safety acknowledgement that_DisplayBackupshows before exposing mnemonic material.Possible stopgap
async def intro(self, num_of_words: int | None = None) -> None: - # TODO: design/copy - pass + from trezor.ui.layouts.reset import show_intro_backup + + await show_intro_backup(num_of_words=num_of_words) async def backup(self, iter_shares: Iterable[ShareInfo]) -> None: - # TODO: warn user about safety + from trezor.ui.layouts.reset import show_warning_backup + + await show_warning_backup() # backup all shares for share in iter_shares: await self._backup_share(share)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/src/apps/management/reset_device/layout.py` around lines 237 - 246, The intro() method is a no-op and backup() immediately calls _backup_share, which skips the existing _DisplayBackup disclosure/safety acknowledgement used in the N4W1 flow; update intro() to present the same disclosure/acknowledgement flow used by _DisplayBackup (or call the common method/component that shows the copy/safety notice) and modify backup() to ensure it awaits/uses intro() before iterating shares (i.e., call await self.intro(...) or invoke the shared disclosure helper before calling self._backup_share for each ShareInfo) so the safety intro is always shown prior to exposing mnemonic material.core/src/apps/management/recovery_device/layout.py (1)
283-291:⚠️ Potential issue | 🟡 MinorDon't warn on the default
method=Nonepath.In non-N4W1 builds this still logs
Unsupported backup method: Nonefor the ordinary recovery flow. Restrict the warning to explicit non-Noneunsupported values.Proposed patch
- if method is not BackupMethod.Display and __debug__: + if method not in (None, BackupMethod.Display) and __debug__: from trezor import log log.warning(__name__, "Unsupported backup method: %s", method)Based on learnings,
method=Noneis an intentional placeholder and should not trigger unsupported-method warnings.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/src/apps/management/recovery_device/layout.py` around lines 283 - 291, The warning should not fire for the intentional placeholder value None; update choose_handler so the log.warning inside the __debug__ block only runs when method is not None and not BackupMethod.Display. Locate the choose_handler function (and the log.warning call that references Unsupported backup method) and change the condition to require method is not None in addition to method is not BackupMethod.Display so only explicit unsupported enum values trigger the warning; keep returning _DisplayHandler unchanged.
🧹 Nitpick comments (1)
tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.py (1)
38-39: Threadbackup_methodthrough the reset leg too.The new fixture only affects the recovery half of this roundtrip.
reset(session)still uses the default display backup flow, so the N4W1 matrix here never exercises reset-time backup.Proposed patch
-def test_reset_recovery(client: Client, backup_method: BackupMethod): +def test_reset_recovery(client: Client, backup_method: BackupMethod): session = client.get_seedless_session() - mnemonics = reset(session) + mnemonics = reset(session, method=backup_method) session = client.get_session() address_before = btc.get_address(session, "Bitcoin", parse_path("m/44h/0h/0h/0/0"))-def reset(session: Session, strength: int = 128) -> list[str]: +def reset( + session: Session, + strength: int = 128, + method: BackupMethod = BackupMethod.Display, +) -> list[str]: with session.test_ctx as client: - IF = InputFlowSlip39BasicResetRecovery(session) + IF = InputFlowSlip39BasicResetRecovery(session, method) client.set_input_flow(IF.get()) device.setup( session, strength=strength, passphrase_protection=False, pin_protection=False, label="test", backup_type=BackupType.Slip39_Basic, + backup_method=method, entropy_check_count=0, _get_entropy=MOCK_GET_ENTROPY, )Also applies to: 52-52
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.py` around lines 38 - 39, The test only applies the backup_method fixture to the recovery half; thread the backup_method through the reset leg so reset uses the same backup flow. Update the test_reset_recovery code paths that call client.get_seedless_session() and reset(session) to pass the backup_method fixture into the reset flow (for example by calling reset(session, backup_method=backup_method) or by creating the session with client.get_seedless_session(backup_method=backup_method) so both get_seedless_session and reset() use the same backup_method); do the same change for the other occurrence referenced (around line 52) so both reset and recovery phases exercise the selected backup_method.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@core/src/apps/management/recovery_device/layout.py`:
- Around line 283-291: The warning should not fire for the intentional
placeholder value None; update choose_handler so the log.warning inside the
__debug__ block only runs when method is not None and not BackupMethod.Display.
Locate the choose_handler function (and the log.warning call that references
Unsupported backup method) and change the condition to require method is not
None in addition to method is not BackupMethod.Display so only explicit
unsupported enum values trigger the warning; keep returning _DisplayHandler
unchanged.
In `@core/src/apps/management/reset_device/layout.py`:
- Around line 237-246: The intro() method is a no-op and backup() immediately
calls _backup_share, which skips the existing _DisplayBackup disclosure/safety
acknowledgement used in the N4W1 flow; update intro() to present the same
disclosure/acknowledgement flow used by _DisplayBackup (or call the common
method/component that shows the copy/safety notice) and modify backup() to
ensure it awaits/uses intro() before iterating shares (i.e., call await
self.intro(...) or invoke the shared disclosure helper before calling
self._backup_share for each ShareInfo) so the safety intro is always shown prior
to exposing mnemonic material.
---
Nitpick comments:
In `@tests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.py`:
- Around line 38-39: The test only applies the backup_method fixture to the
recovery half; thread the backup_method through the reset leg so reset uses the
same backup flow. Update the test_reset_recovery code paths that call
client.get_seedless_session() and reset(session) to pass the backup_method
fixture into the reset flow (for example by calling reset(session,
backup_method=backup_method) or by creating the session with
client.get_seedless_session(backup_method=backup_method) so both
get_seedless_session and reset() use the same backup_method); do the same change
for the other occurrence referenced (around line 52) so both reset and recovery
phases exercise the selected backup_method.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8e18c8a3-1132-4528-869b-a022ee1ab303
📒 Files selected for processing (19)
.github/workflows/core.ymlcore/src/apps/management/recovery_device/layout.pycore/src/apps/management/reset_device/layout.pycore/src/trezor/ui/layouts/eckhart/recovery.pytests/conftest.pytests/device_tests/reset_recovery/test_recovery_bip39_dryrun.pytests/device_tests/reset_recovery/test_recovery_bip39_t2.pytests/device_tests/reset_recovery/test_recovery_slip39_advanced.pytests/device_tests/reset_recovery/test_recovery_slip39_advanced_dryrun.pytests/device_tests/reset_recovery/test_recovery_slip39_basic.pytests/device_tests/reset_recovery/test_recovery_slip39_basic_dryrun.pytests/device_tests/reset_recovery/test_reset_recovery_bip39.pytests/device_tests/reset_recovery/test_reset_recovery_slip39_advanced.pytests/device_tests/reset_recovery/test_reset_recovery_slip39_basic.pytests/device_tests/reset_recovery/test_reset_slip39_basic.pytests/device_tests/test_repeated_backup.pytests/input_flows.pytests/input_flows_helpers.pytests/ui_tests/fixtures.json
✅ Files skipped from review due to trivial changes (4)
- tests/device_tests/reset_recovery/test_recovery_bip39_t2.py
- tests/device_tests/reset_recovery/test_recovery_bip39_dryrun.py
- tests/device_tests/reset_recovery/test_reset_recovery_slip39_advanced.py
- tests/device_tests/reset_recovery/test_recovery_slip39_advanced.py
🚧 Files skipped from review as they are similar to previous changes (6)
- tests/device_tests/reset_recovery/test_recovery_slip39_advanced_dryrun.py
- tests/device_tests/reset_recovery/test_reset_recovery_bip39.py
- tests/device_tests/reset_recovery/test_recovery_slip39_basic_dryrun.py
- tests/input_flows_helpers.py
- tests/conftest.py
- tests/input_flows.py




























































































































































Design: https://satoshilabs.slack.com/docs/T0J8V2YBY/F0AKE51PVFZ
Also add N4W1-enabled builds to the CI for integration tests.
I have used https://docs.pytest.org/en/stable/how-to/fixtures.html#parametrizing-fixtures in order to re-use some of the existing backup/recovery device tests in a separate CI job:
The rest of the test-related changes will be done in separate PRs.